# -*- coding: utf-8 -*-
# @Time    : 2024/6/20 17:53
# @Author  : yujiahao
# @File    : 01_pandas_series.py
# @description:Series 结构

'''

Series 结构：

    Series 结构，也称 Series 序列，是 Pandas 常用的数据结构之一，它是一种类似于一维数组的结构，由一组数据值（value）和一组标签组成，其中标签与数据值之间是一一对应的关系。
    Series 的数据结构是非常有用的，因为它可以处理各种数据类型，同时保持了高效的数据操作能力。
    Series 可以保存任何数据类型，比如整数、字符串、浮点数、Python 对象等，它的标签默认为整数，从 0 开始依次递增。Series 的结构图，如下所示：

        通过标签我们可以更加直观地查看数据所在的索引位置。

            +---+----------+
            |   |          |
            | 0 | 0.801000 |  <- 数据值 (蓝色部分)
            | 1 | 0.033423 |  <- 数据值 (蓝色部分)
            | 2 | 0.757575 |  <- 数据值 (蓝色部分)
            | 3 | 0.474346 |  <- 数据值 (蓝色部分)
            | 4 | 0.076785 |  <- 数据值 (蓝色部分)
            | 5 | 0.395737 |  <- 数据值 (蓝色部分)
            +---+----------+
              ^      ^
              |      |
            标签      数据值 (红色部分)
             (红色部分)

            dtype: float64  <- 数据值类型 (绿色部分)

Series 特点：

    - 一维数组：Series 是一维的，这意味着它只有一个轴（或维度），类似于 Python 中的列表。
    - 索引：每个 Series 都有一个索引，它可以是整数、字符串、日期等类型。如果不指定索引，Pandas 将默认创建一个从 0 开始的整数索引。
    - 数据类型：Series 可以容纳不同数据类型的元素，包括整数、浮点数、字符串、Python 对象等。
    - 大小不变性：Series 的大小在创建后是不变的，但可以通过某些操作（如 append 或 delete）来改变。
    - 操作：Series 支持各种操作，如数学运算、统计分析、字符串处理等。
    - 缺失数据：Series 可以包含缺失数据，Pandas 使用 NaN（Not a Number）来表示缺失或无值。


Series 属性：

        名称       | 属性
        -----------|-------------------------------------------------
        axes       | 以列表的形式返回所有行索引标签。
        dtype      | 返回对象的数据类型。
        empty      | 如果 Series 为空，则返回 True，否则返回 False。
        ndim       | 返回输入数据的维数。
        size       | 返回输入数据的元素数量。
        values     | 以 ndarray 的形式返回 Series 对象的数据。
        index      | 返回一个 RangeIndex 对象，用来描述索引的取值范围。

创建Series：【首先要导入pandas模块，没有就安装一下这里不说了。】


        ① 可以使用 pd.Series() 构造函数创建一个 Series 对象，传递一个数据数组（可以是列表、NumPy 数组等）和一个可选的索引数组。

            pandas.Series(data=None, index=None, dtype=None, name=None, copy=False, fastpath=False)

            参数说明：

                - data：Series 的数据部分，可以是列表、数组、字典、标量值等。如果不提供此参数，则创建一个空的 Series。
                - index：Series 的索引部分，用于对数据进行标记。可以是列表、数组、索引对象等。如果不提供此参数，则创建一个默认的整数索引。
                - dtype：指定 Series 的数据类型。可以是 NumPy 的数据类型，例如 np.int64、np.float64 等。如果不提供此参数，则根据数据自动推断数据类型。
                - name：Series 的名称，用于标识 Series 对象。如果提供了此参数，则创建的 Series 对象将具有指定的名称。
                - copy：是否复制数据。默认为 False，表示不复制数据。如果设置为 True，则复制输入的数据。
                - fastpath：是否启用快速路径。默认为 False。启用快速路径可能会在某些情况下提高性能。


        ② ndarray创建Series对象

            ndarray 是 NumPy 中的数组类型，当 data 是 ndarry 时，传递的索引必须具有与数组相同的长度。假如没有给 index 参数传参，在默认情况下，索引值将使用是 range(n) 生成，其中 n 代表数组长度
            索引默认从 0 开始分配 ，其索引范围为 0 到len(data)-1。这种设置方式被称为“隐式索引”。

               data : [0,1,2,3…. range(len(array))-1]创建还是用pandas的Series()

            也可以使用“显式索引”的方法定义索引标签，就是在创建的时候，显示的指定索引值：

                s = pd.Series(data,index=[100,101,102,103])
        ③ dict创建Series对象

            如果不指定索引的话，会按照字典的键来构造索引，如果指定了索引就需要将索引标签与字典中的值一一对应。
                使用同②一样

        ④ 标量创建Series对象

            标量值就是一个单一的数值（如 5），而不是一个数组、列表或其他数据结构。
            由此，我们在创建Series的时候一定要指定索引，不同索引对的值是一定的，因为是标量嘛



【这里了解一下NumPy】 https://c.biancheng.net/numpy/

    NumPy（Numerical Python）是 Python 中用于科学计算的一个开源库，它提供了多维数组对象（例如数组）、用于数组操作的函数以及线性代数等工具。
    以下是 NumPy 的一些重要特性和用途：

        - 多维数组（Arrays）：NumPy 提供了一个名为 ndarray 的多维数组对象，它是具有相同数据类型（通常是数值类型）的元素的多维容器。这使得 NumPy 可以高效地存储和操作大型数据集。
        - 广播（Broadcasting）：NumPy 支持广播操作，允许在不同形状的数组之间执行算术运算，而无需显式循环。
        - 矢量化操作：NumPy 支持矢量化操作，允许对整个数组执行算术和其他操作，而无需编写显式循环。
        - 线性代数操作：NumPy 提供了用于线性代数运算的函数，包括矩阵乘法、求逆、特征值分解等。
        - 随机数生成器：NumPy 包含一个随机数模块，用于生成各种随机数分布的样本。
        - 整合其他语言：NumPy 是基于 C 语言编写的，因此在性能方面表现出色，并且可以轻松与其他基于 C 或 Fortran 的库集成。
        - 科学计算：NumPy 广泛应用于科学计算、数据分析、机器学习、图像处理等领域。
'''

import pandas as pd
import numpy as np


# todo 1、Series的创建
def create_series():
    '''
    Series的创建
    :return:
    '''

    # 1、创建一个空的Series

    se = pd.Series()
    print(f'创建一个空的Series:\t{se}')

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 2、使用ndarray创建Series对象
    data = np.array(['a', 'b', 'c', 'd', 'e'])

    # ① 使用默认索引，自己会从 0 轮询到 length - 1
    print(f'使用nd默认索引创建Series:\n{pd.Series(data)}')

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # ② 制定索引 (一一对应，不能多)
    print(f'使用nd指定索引创建Series:\n{pd.Series(data, index=[1, 2, 3, 4, 5])}')

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 3、使用字典创建
    dict = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}

    # ① 没有指定索引
    print(f'使用dict没有指定索引创建Series:\n{pd.Series(dict)}')

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # ② 制定索引 (这个索引是字典的key，如果不是则对应的值为NaN)
    # 声明索引
    index_list = ['b', 2, 'c', 4, 5, 8]

    print(f'使用dict指定索引创建Series:\n{pd.Series(dict, index=index_list)}')

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# todo 2、Series的访问

def select_series():
    data = pd.Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e'])

    # ① 通过位置索引访问（和list一样就是通过角标访问，角标从0开始）

    print(f'通过角标切片进行访问：\n{data[:3]}')

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # ② 通过索引标签访问(就是我们指定的Series的索引)

    '''Series 类似于固定大小的 dict，把 index 中的索引标签当做 key，而把 Series 序列中的元素值当做 value，然后通过 index 索引标签来访问或者修改元素值。'''
    # 可以一次性访问多个，使用列表形式，另外的如果使用了 index 中不包含的标签，则会触发异常。
    print(f'通过索引标签访问多个值：\n{data[["a", "e"]]}')

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # ③ 索引和值的对应关系
    for index, value in data.items():
        print(f"Index: {index}, Value: {value}")


# todo 3、Series的属性
def attribute():
    # 随机创建一个长度为5的Series
    data = pd.Series(np.random.randn(5))
    print(data)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 下面测试一下属性
    print(f"测试 Series 的 axes 属性， 以列表的形式返回所有行索引标签\n{data.axes}")

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # dtype      | 返回对象的数据类型。
    print(f"测试 Series 的 dtype 属性， 返回对象的数据类型\n{data.dtype}")

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # empty      | 如果 Series 为空，则返回 True，否则返回 False。
    print(f"测试 Series 的 empty 属性， 如果 Series 为空，则返回 True，否则返回 False\n{data.empty}")

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # ndim       | 返回输入数据的维数。
    print(f"测试 Series 的 ndim 属性， 返回输入数据的维数\n{data.ndim}")

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # size       | 返回输入数据的元素数量。
    print(f"测试 Series 的 size 属性， 返回输入数据的元素数量\n{data.size}")

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # values     | 以 ndarray 的形式返回 Series 对象的数据。
    print(f"测试 Series 的 values 属性， 以 ndarray 的形式返回 Series 对象的数据\n{data.values}")

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # index      | 返回一个 RangeIndex 对象，用来描述索引的取值范围。
    print(f"测试 Series 的 index 属性， 返回一个 RangeIndex 对象，用来描述索引的取值范围\n{data.index}")

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 获取最大值和最小值的索引
    print(f'获取最大值的索引: {data.idxmax()}')
    print(f'最小值的索引{data.idxmin()}')


# todo 4、一些方法
def func():
    # 1) head()&tail()查看数据

    '''如果想要查看 Series 的某一部分数据，可以使用 head() 或者 tail() 方法。其中 head() 返回前 n 行数据，默认显示前 5 行数据。示例如下：'''

    s = pd.Series(np.random.randn(5))
    print("The original series is:")
    print(s)
    # 返回前三行数据 默认5条
    print(s.head(3))

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 输出后两行数据 默认5条
    print(s.tail(2))

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 2) isnull()&nonull()检测缺失值

    '''
    isnull() 和 nonull() 用于检测 Series 中的缺失值。所谓缺失值，顾名思义就是值不存在、丢失、缺少。
    
        - isnull()：如果为值不存在或者缺失，则返回 True。
        - notnull()：如果值不存在或者缺失，则返回 False。
    '''

    # None代表缺失数据
    data = pd.Series([1, 2, 5, None])

    print(pd.isnull(data))  # 是空值返回True

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    print(pd.notnull(data))  # 空值返回False

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 3)获取描述统计信息
    '''
    获取描述统计信息：s.describe() 是 Pandas Series 对象的一个方法，用于计算该 Series 的描述统计信息，包括计数、均值、标准差、最小值、25%、50%、75% 四分位数以及最大值。
    计数（count）：非缺失值的数量。
    均值（mean）：所有值的平均值。
    标准差（std）：所有值的标准差。
    最小值（min）：所有值中的最小值。
    25%、50%、75% 四分位数：分别表示数据中值的 25%、50%（中位数）、75% 的位置。
    最大值（max）：所有值中的最大值。
    '''
    print(f'获取描述统计信息:{s.describe()}')

    # 4)转换数据类型：使用 astype 方法将 Series 转换为另一种数据类型。
    s = s.astype('float64')  # 将 Series 中的所有元素转换为 float64 类型

    # 其他属性和方法
    '''
    shape 属性返回一个元组，描述数据结构的维度。
        对于 Series，shape 返回一个包含单个值的元组，表示 Series 中的元素数量。
        对于 DataFrame，shape 返回一个包含两个值的元组，分别表示行数和列数。
    '''
    print(f'形状: {s.shape}')  # 形状


# todo 5、Series的修改

def updata_series():
    data = pd.Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e'])

    print(f'打印一下原始Series\n{data}')

    # 增加一个值
    data['f'] = 789

    print(f'打印一下Series\n{data}')

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 修改
    data['f'] = 345

    print(f'打印一下Series\n{data}')

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 使用 del 删除指定索引标签的元素。
    del data['a']  # 删除索引标签 'a' 对应的元素
    print(f'del 方法删除指定索引值之后的：\n{data}')

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 使用 drop 方法删除一个或多个索引标签，并返回一个新的 Series。
    s_dropped = data.drop(['b', 'f'])  # 返回一个删除了索引标签 'b' 的新 Series
    print(f'drop 方法删除指定索引值之后的：{s_dropped}')


# todo 6、基本运算
def operations():
    s = pd.Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e'])

    # 算术运算
    result = s * 2  # 所有元素乘以2
    print(result)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 过滤
    filtered_series = s[s > 2]  # 选择大于2的元素
    print(filtered_series)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 数学函数

    print(f'对每个元素取平方根: \n{np.sqrt(s)}')
    print(f'求和: {s.sum()}')  # 求和
    print(f'平均值: {s.mean()}')  # 平均值
    print(f'标准差: {s.std()}')  # 标准差
    print(f'最小值: {s.min()}')  # 最小值
    print(f'最大值: {s.max()}')  # 最大值


def main():
    # create_series()
    # select_series()
    # attribute()
    # func()
    # updata_series()
    operations()


if __name__ == '__main__':
    main()
